package net.brucecooper.mindmap.client;

import gwt.canvas.client.Canvas;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import com.google.gwt.event.dom.client.BlurEvent;
import com.google.gwt.event.dom.client.BlurHandler;
import com.google.gwt.event.dom.client.KeyPressEvent;
import com.google.gwt.event.dom.client.KeyPressHandler;
import com.google.gwt.event.dom.client.MouseDownEvent;
import com.google.gwt.event.dom.client.MouseDownHandler;
import com.google.gwt.user.client.Command;
import com.google.gwt.user.client.DeferredCommand;
import com.google.gwt.user.client.ui.TextBox;

public class NodeWidget extends HorizontalPanel {
    enum DragDecoration {
        NONE, TARGET, NO_TARGET
    }

    private static final int SNAKE_WIDTH = 10;

    private int nodeID;
    NodeTextWidget theNode;
    GraphicComponent rightPanel;
    GraphicComponent leftPanel;
    TextBox editor;
    NodeWidget parentNodeWidget;
    private NodeOrientation orientation;
    private NodeDataStore dataStore;
    private MindMap mindmap;
    private boolean dropTarget;
    private boolean dropProhibited;
    ImageComponent plusMinusButtonLeft;
    ImageComponent plusMinusButtonRight;
    private int level;

    public NodeWidget(MindMap mindmap, NodeWidget parentNodeWidget, NodeDataStore dataStore, int nodeID, NodeOrientation orientation, int level) {
        this.mindmap = mindmap;
        this.parentNodeWidget = parentNodeWidget;
        this.orientation = orientation;
        this.nodeID = nodeID;
        this.dataStore = dataStore;
        this.setLevel(level);

        List<Integer> leftChildren = null;
        List<Integer> rightChildren = null;

        switch (orientation) {
        case CENTER:
            leftChildren = new ArrayList<Integer>();
            rightChildren = new ArrayList<Integer>();
            boolean left = false;
            for (int child : dataStore.getNodeChildren(nodeID)) {
                if (left) {
                    leftChildren.add(child);
                } else {
                    rightChildren.add(child);
                }
                left = !left;
            }
            break;
        case LEFT:
            leftChildren = dataStore.getNodeChildren(nodeID);
            rightChildren = Collections.emptyList();
            break;
        case RIGHT:
            rightChildren = dataStore.getNodeChildren(nodeID);
            leftChildren = Collections.emptyList();
            break;
        }

        if (leftChildren.size() > 0) {
            boolean collapsed = mindmap.isCollapsed(nodeID);
            
            if (!collapsed) {
                leftPanel = createPanel(dataStore, leftChildren, NodeOrientation.LEFT);
                add(leftPanel);
                add(new Spacer(SNAKE_WIDTH)); //Add space for the snakes
            }
            plusMinusButtonLeft = new CollapseExpandButton(this, collapsed);
            add(plusMinusButtonLeft);
        }

        theNode = new NodeTextWidget(this);

        add(theNode);
        if (rightChildren.size() > 0) {
            boolean collapsed = mindmap.isCollapsed(nodeID);
            plusMinusButtonRight = new CollapseExpandButton(this, collapsed);
            add(plusMinusButtonRight);
            
            if (!collapsed) {
                rightPanel = createPanel(dataStore, rightChildren, NodeOrientation.RIGHT);
                add(new Spacer(SNAKE_WIDTH)); // Space for the snakes
                add(rightPanel);
            }
        }
    }

    @Override
    public void layout(Canvas canvas) {
        super.layout(canvas);
        // We want to override the y positions of the plus/minus buttons because they don't go in the "middle" necessarily.
        if (plusMinusButtonLeft != null) {
            plusMinusButtonLeft.y = getLineExitY(canvas)-plusMinusButtonLeft.height/2;
        }
        if (plusMinusButtonRight != null) {
            plusMinusButtonRight.y = getLineExitY(canvas)-plusMinusButtonRight.height/2;
        }
    }
    
    @Override
    public int getPreferredHeight(Canvas canvas) {
        int h = super.getPreferredHeight(canvas);
        
        // We need to cater for the potential that the plusMinusButton may be slightly outside the normal layout area, in which case the preferredHeight should be a wee bit more... 
        if (plusMinusButtonLeft != null) {
            int xh = plusMinusButtonLeft.getY() + plusMinusButtonLeft.getPreferredHeight(canvas);
            if (xh > h)
                h = xh;
        }
        if (plusMinusButtonRight != null) {
            int xh = plusMinusButtonRight.getY() + plusMinusButtonRight.getPreferredHeight(canvas);
            if (xh > h)
                h = xh;
        }
        return h;
    }

    private void fixFocus() {
        if (mindmap != null) {
            mindmap.getCanvas().setFocus(true);
        }
    }

    public NodeOrientation getOrientation() {
        return orientation;
    }

    public NodeWidget getNodeWidget(int id) {
        if (nodeID == id) {
            return this;
        } else {
            if (leftPanel != null) {
                for (int i = 0; i < leftPanel.getWidgetCount(); i++) {
                    GraphicComponent w = leftPanel.getWidget(i);
                    NodeWidget test = ((NodeWidget) w).getNodeWidget(id);

                    if (test != null)
                        return test;
                }
            }
            if (rightPanel != null) {
                for (int i = 0; i < rightPanel.getWidgetCount(); i++) {
                    GraphicComponent w = rightPanel.getWidget(i);
                    NodeWidget test = ((NodeWidget) w).getNodeWidget(id);

                    if (test != null)
                        return test;
                }
            }
            return null;
        }
    }

    void startEditing() {
        getSelectionManager().select(getNodeID());
        editor = new TextBox();
        // Because our component is in a focus panel, we need to make sure that
        // any mouse down
        // events are not interpreted as a click on the panel itself.
        editor.addMouseDownHandler(new MouseDownHandler() {
            public void onMouseDown(MouseDownEvent event) {
                event.stopPropagation();
            }
        });

        editor.setText(getDataStore().getNodeName(nodeID));

        editor.addKeyPressHandler(new KeyPressHandler() {
            public void onKeyPress(KeyPressEvent event) {
                event.stopPropagation();
                switch (event.getCharCode()) {
                case 27:
                    cancelEditing();
                    break;
                case 10:
                case 13:
                    commitEditing();
                    break;
                }
            }

        });
        Point coords = theNode.getRootCoordinates(new Point(0,0));
        mindmap.addEditor(editor, coords.x, coords.y, theNode.getWidth(), Math.max(theNode.getHeight(), 14));
        
        editor.addBlurHandler(new BlurHandler() {
            public void onBlur(BlurEvent event) {
                commitEditing();
            }
        });

        // Defer focus and select, to give the renderer a chance to place the
        // component
        DeferredCommand.addCommand(new Command() {
            public void execute() {
                editor.setFocus(true);
                editor.selectAll();
            }
        });

    }

    private NodeSelectionManager getSelectionManager() {
        return mindmap.getSelectionManager();
    }

    NodeWidget getParentNodeWidget() {
        return parentNodeWidget;
    }

    private void cancelEditing() {
//    	Logger.log("Cancelling editing");
        mindmap.removeEditor(editor);
        DeferredCommand.addCommand(new Command() {
            public void execute() {
                fixFocus();
            }
        });
    }

    private void commitEditing() {
        String newName = editor.getText();
        if (!newName.equals(theNode.getText())) {
            theNode.setText(newName);
            getDataStore().setNodeName(nodeID, newName);
            // The re-layout will have created a new node to represent us. We'll
            // select that
            getSelectionManager().select(nodeID);
        }
        cancelEditing();
    }
    
    private void drawSnake(Canvas ctx, int x1, int y1, int x2, int y2) {
    	ctx.beginPath();
    	ctx.moveTo(x1,y1);
    	ctx.cubicCurveTo(x2, y1, x1, y2, x2, y2);
        ctx.stroke();
    }
    
    @Override
    public void paintComponent(Canvas ctx, Clip clip) {
    	// All the children will have already been painted.  This is for decorations
    	if (leftPanel != null ) {
    	    int x = leftPanel.getX() + leftPanel.getWidth();
    	    if (clip.intersects(x, leftPanel.getY(), SNAKE_WIDTH, leftPanel.getHeight())) {
        		ctx.save();
        		ctx.translate(x,0);
        		drawLinesCanvas(ctx, leftPanel, true);
        		ctx.restore();
    	    }
    	}
    	
    	if (rightPanel != null) {
            int x = plusMinusButtonRight.getX()+plusMinusButtonRight.getWidth();
            if (clip.intersects(x, rightPanel.getY(), SNAKE_WIDTH, rightPanel.getHeight())) {
        		ctx.save();
        		ctx.translate(x,0);
        		drawLinesCanvas(ctx, rightPanel, false);
        		ctx.restore();
            }
    	}
    }
    
    
    private int getLineExitY(Canvas canvas) {
        return theNode.getY() + (dataStore.getNodeOutline(nodeID).usesBaseline ? theNode.getBaseline(canvas) : theNode.getPreferredHeight(canvas)/2);
    }

    private void drawLinesCanvas(Canvas nodeLinks, GraphicComponent panel, boolean left) {
        nodeLinks.setStrokeStyle("darkGreen");
        
        int myBaseline = getLineExitY(nodeLinks);
        
        for (int i = 0; i < panel.getWidgetCount(); i++) {
        	GraphicComponent comp = panel.getWidget(i);
            NodeWidget nw = (NodeWidget) comp;
            int verticalOffset = nw.getY() + nw.getLineExitY(nodeLinks);

            if (left) {
                drawSnake(nodeLinks, 
                		0, 
                		panel.getY() + verticalOffset, 
                		SNAKE_WIDTH, 
                		myBaseline
                		);
            } else {
                drawSnake(nodeLinks, 
                		0, 
                        myBaseline, 
                		SNAKE_WIDTH, 
                		panel.getY() + verticalOffset);
            }
        }
    }

    private Panel createPanel(NodeDataStore dataStore, List<Integer> nodes, NodeOrientation orientation) {

    	Panel panel = new VerticalPanel(orientation == NodeOrientation.LEFT ? Panel.Alignment.RIGHT : Panel.Alignment.LEFT);
    	switch (level) {
    	case 0:
            panel.setSpacing(10);
            break;
        case 1:
            panel.setSpacing(8);
            break;
        case 2:
            panel.setSpacing(4);
            break;
	    default:
	        panel.setSpacing(2);
	        break;

    	}
        for (int i = 0; i < nodes.size(); i++) {
            NodeWidget childWidget = new NodeWidget(mindmap, this, dataStore, nodes.get(i), orientation, this.getLevel()+1);
            panel.add(childWidget);
        }
        return panel;
    }

    public boolean isSelected() {
    	return mindmap.getSelectionManager().getSelected() == nodeID;
    }
    
    public void setDropProhibited(boolean dropProhibited) {
        this.dropProhibited = dropProhibited;
        for (NodeWidget w: getAllChildNodes()) {
            w.setDropProhibited(dropProhibited);
        }
    }
    
    public boolean isDropProhibited() {
        return dropProhibited;
    }


    public void setDropTarget(boolean dropTarget) {
        this.dropTarget = dropTarget;
    }

    public boolean isDropTarget() {
        return dropTarget;
    }

    int indexOf(GraphicComponent w, GraphicComponent p) {
        for (int i = 0; p != null && i < p.getWidgetCount(); i++) {
            if (p.getWidget(i).equals(w))
                return i;
        }
        return -1;
    }

    public NodeWidget getPreviousSibling(GraphicComponent sel) {
        NodeWidget next = null;
        ;

        int index = indexOf(sel, leftPanel);
        if (index != -1) {
            next = index == 0 ? null : (NodeWidget) leftPanel.getWidget(index - 1);
        } else {
            index = indexOf(sel, rightPanel);
            if (index != -1) {
                next = index == 0 ? null : (NodeWidget) rightPanel.getWidget(index - 1);
            }
        }
        return next;
    }

    public NodeWidget getNextSibling(GraphicComponent sel) {
        NodeWidget next = null;
        ;

        int index = indexOf(sel, leftPanel);
        if (index != -1) {
            next = index >= leftPanel.getWidgetCount() - 1 ? null : (NodeWidget) leftPanel.getWidget(index + 1);
        } else {
            index = indexOf(sel, rightPanel);
            if (index != -1) {
                next = index >= rightPanel.getWidgetCount() - 1 ? null : (NodeWidget) rightPanel.getWidget(index + 1);
            }
        }
        return next;
    }

    public NodeWidget getFirtLeftChild() {
        return leftPanel == null || leftPanel.getWidgetCount() == 0 ? null : (NodeWidget) leftPanel.getWidget(0);
    }

    public NodeWidget getFirtRightChild() {
        return rightPanel == null || rightPanel.getWidgetCount() == 0 ? null : (NodeWidget) rightPanel.getWidget(0);
    }

    public int getNodeID() {
        return nodeID;
    }

    /**
     * 
     * @param x
     *            X position in absolute document pixel terms
     * @param y
     *            Y position in absolute document pixel terms
     * @return
     */
    public NodeWidget getNodeOver(int x, int y) {
        int nodeAbsX = theNode.getX();
        int nodeAbsY = theNode.getY();
        int nodeWidth = theNode.getWidth();
        int nodeHeight = theNode.getHeight();

        if (x >= nodeAbsX && x < nodeAbsX + nodeWidth && y >= nodeAbsY && y < nodeAbsY + nodeHeight) {
            return this;
        }

        List<NodeWidget> children = getAllChildNodes();
        for (GraphicComponent w : children) {
            NodeWidget found = ((NodeWidget) w).getNodeOver(x, y);
            if (found != null)
                return found;
        }

        return null;
    }

    private List<NodeWidget> getAllChildNodes() {
        List<NodeWidget> result = new ArrayList<NodeWidget>();
        if (leftPanel != null) {
            for (int i = 0; i < leftPanel.getWidgetCount(); i++) {
                GraphicComponent gc = leftPanel.getWidget(i);
                result.add((NodeWidget) gc);
            }
        }
        if (rightPanel != null) {
            for (int i = 0; i < rightPanel.getWidgetCount(); i++) {
                GraphicComponent gc = rightPanel.getWidget(i);
                result.add((NodeWidget) gc);
            }
        }
        return result;
    }


	public NodeDataStore getDataStore() {
		return dataStore;
	}



    public void setLevel(int level) {
        this.level = level;
    }



    public int getLevel() {
        return level;
    }


}
